#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>


/*
作用：答应错误位置与结束
参数：1.打印内容
返回值：
*/
void perr_exit(const char *s)
{
	perror(s);
	exit(-1);
}


/*
作用：与服务端建立连接
参数：1.socket 文件描述符
	 2.服务器的地址结构
	 2.服务器的地址结构大小
返回值：0 成功，-1 失败
*/
int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;
    n = connect(fd, sa, salen);
	if (n < 0) {
		perr_exit("connect error");
    }

    return n;
}
/*
作用：阻塞等待客户端建立连接
参数：1.socket 文件描述符
	 2.传出客户端的IP地址和端口号
	 3.传入传出（传入sockaddr结构体大小，传出客户端地址结构大小）
返回值：其他 成功 返回一个新的套接字来和客户端通信，-1 失败
*/
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
	int n;

again:
	if ((n = accept(fd, sa, salenptr)) < 0) {
		if ((errno == ECONNABORTED) || (errno == EINTR))
			goto again;
		else
			perr_exit("accept error");
	}
	return n;
}


/*
作用：未连接的套接字转换成一个被动套接字，接受来自其他主动套接字的连接请求。
参数：1.socket 文件描述符
	 2.请求队列的最大长度（<=128）
返回值：0 成功，-1 失败
*/
int Listen(int fd, int backlog)
{
    int n;

	if ((n = listen(fd, backlog)) < 0)
		perr_exit("listen error");

    return n;
}


/*
作用：给socket绑定 地址结构（ip+port）
参数：1.socket 文件描述符
	 2.sockaddr 结构体变量的指针
	 3.addr 变量的大小，可由 sizeof() 计算得出。
返回值：0 成功绑定，-1 失败
*/
int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

	if ((n = bind(fd, sa, salen)) < 0)
		perr_exit("bind error");

    return n;
}


/*
作用：创建一个套接字
参数：1.地址族，常见AF_INET（IPv4），AF_INET6（IPv6）
	 2.数据传输方式/套接字类型，SOCK_STREAM（流格式套接字/面向连接的套接字），SOCK_DGRAM（数据报套接字/无连接的套接字）
	 3.传输协议，0表示根据前两参数匹配默认协议
返回值：其他 成功创建的对应文件描述符，-1 创建失败；
*/
int Socket(int family, int type, int protocol)
{
	int n;

	if ((n = socket(family, type, protocol)) < 0)
		perr_exit("socket error");

	return n;
}


/*
作用：读取文件描述符中的nbytes个字节（可以不读满n个字节）
参数：1.socket 文件描述符
	 2.读上来的数据保存在缓冲区ptr中
	 3.请求读取的字节数
返回值：其他 成功返回读取的字节数，0 读写位置已到达文件末尾，-1 失败
*/
ssize_t Read(int fd, void *ptr, size_t nbytes)
{
	ssize_t n;

again:
	if ( (n = read(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}

	return n;
}


/*
作用：写入文件描述符中nbytes个字节（可以不写满n个字节）
参数：1.socket 文件描述符
	 2.写入缓冲区ptr中
	 3.请求写入的字节数
返回值：其他 成功返回写入的字节数，0 读写位置已到达文件末尾，-1 失败
*/
ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
	ssize_t n;

again:
	if ((n = write(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}


/*
作用：关闭文件或网络设备
参数：1.socket 文件描述符
返回值：0 关闭成功，-1 失败
*/
int Close(int fd)
{
    int n;
	if ((n = close(fd)) == -1)
		perr_exit("close error");

    return n;
}


/*
作用：读取文件描述符中的n个字节（读满n个字节）
参数：1.socket 文件描述符
	 2.读上来的数据保存在缓冲区ptr中
	 3.缓存区大小
返回值：其他 成功返回读取的字节数，0 读写位置已到达文件末尾，-1 失败
*/
//socket 4096  readn(cfd, buf, 4096)   nleft = 4096-1500
ssize_t Readn(int fd, void *vptr, size_t n)
{
	size_t  nleft;              //usigned int 剩余未读取的字节数
	ssize_t nread;              //int 实际读到的字节数
	char   *ptr;

	ptr = vptr;
	nleft = n;                  //n 未读取字节数

	while (nleft > 0) {
		if ((nread = read(fd, ptr, nleft)) < 0) {
			if (errno == EINTR)
				nread = 0;
			else
				return -1;
		} else if (nread == 0)
			break;

		nleft -= nread;   //nleft = nleft - nread 
		ptr += nread;
	}
	return n - nleft;
}


/*
作用：写入文件描述符中n个字节（写满n个字节）
参数：1.socket 文件描述符
	 2.写入缓冲区ptr中
	 3.请求写入的字节数
返回值：其他 成功返回写入的字节数，0 读写位置已到达文件末尾，-1 失败
*/
ssize_t Writen(int fd, const void *vptr, size_t n)
{
	size_t nleft;
	ssize_t nwritten;
	const char *ptr;

	ptr = vptr;
	nleft = n;
	while (nleft > 0) {
		if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
			if (nwritten < 0 && errno == EINTR)
				nwritten = 0;
			else
				return -1;
		}
		nleft -= nwritten;
		ptr += nwritten;
	}
	return n;
}


/*
作用：从上次位置读取文件描述符中的n个字节（读满n个字节）
参数：1.socket 文件描述符
	 2.读上来的数据保存在缓冲区ptr中
返回值：其他 成功返回读取的字节数，0 读写位置已到达文件末尾，-1 失败
*/
static ssize_t my_read(int fd, char *ptr)
{
	static int read_cnt;
	static char *read_ptr;
	static char read_buf[100];

	if (read_cnt <= 0) {
again:
		if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {   //"hello\n"
			if (errno == EINTR)
				goto again;
			return -1;
		} else if (read_cnt == 0)
			return 0;

		read_ptr = read_buf;
	}
	read_cnt--;
	*ptr = *read_ptr++;

	return 1;
}


/*readline --- fgets*/    
//传出参数 vptr
/*
作用：读取文件描述符中一行中的n个字节（可以不读满n个字节）
参数：1.socket 文件描述符
	 2.读上来的数据保存在缓冲区ptr中
	 3.缓存区大小
返回值：其他 成功返回读取的字节数，0 读写位置已到达文件末尾，-1 失败
*/
ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
	ssize_t n, rc;
	char    c, *ptr;
	ptr = vptr;

	for (n = 1; n < maxlen; n++) {
		if ((rc = my_read(fd, &c)) == 1) {   //ptr[] = hello\n
			*ptr++ = c;
			if (c == '\n')
				break;
		} else if (rc == 0) {
			*ptr = 0;
			return n-1;
		} else
			return -1;
	}
	*ptr = 0;

	return n;
}

